/*
 * Copyright (c) 2002, Robert Collins.
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Robert Collins  <rbtcollins@hotmail.com>
 *
 * Modified by yospaly, 2008-12-15
 *
 */

#include <wx/wx.h>
#include <wx/filesys.h>
#include <wx/filename.h>
#include <wx/wfstream.h>
#include <wx/stopwatch.h>
#include <wx/ffile.h>
#include <wx/file.h>
#include <wx/zstream.h>

#include "inibuilder.h"
#include "iniparse.hpp"
#include "bzipstream.h"
#include "wininetstream.h"
#include "events.h"
#include "config.h"
#include "common.h"
#include "mounttable.h"

extern int yyparse ();
void ini_init (wxInputStream&, IniBuilder&);

IniBuilder::IniBuilder() : setup_version(_T("2.673")) {
    p_current_pkg = NULL;
    
    // wxLog::SetActiveTarget(new wxLogStderr());
}

bool IniBuilder::parse() {    
	IniDownloadParseThread* task = new IniDownloadParseThread();
	
	if ( wxTHREAD_NO_ERROR!=task->Create(/*1024*1024*/) ) {
		return false;
	}
	
	task->Run();
	return true;
}

wxThread::ExitCode IniDownloadParseThread::Entry() {
	wxString mirror = Config::get().get_last_mirror();
	if (!mirror.EndsWith(_T("/"))) {
		mirror.Append(_T("/"));
	}
	mirror.Append(_T("setup.bz2"));
	
	size_t downloaded = 0;
	
	// open stream for fetch mirrors from internet
	
	WinINetInputStream inet_stream(mirror);
	if ( !inet_stream.IsOk() ) {
	    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT,
	    		wxString::Format(_("Can't open url: %s"),mirror.c_str()),
	    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
	    );

	    return ExitCode(false);
	}
	
	wxFile setup_bz2;
        const wxString& tempname = wxFileName::CreateTempFileName(_T("Citrus_"), &setup_bz2);
	
	// download to a temp file
	const int BUF_SIZE = 1024*10; // 10K
	char buf[BUF_SIZE] = {0};
	const int MAX_FILE_SIZE = 1024*1024*100; // 100M
	
	size_t readed_len = 0;
    size_t n = inet_stream.GetSize();
    
    const wxString& connect_err = inet_stream.CheckConnection();
    if ( !connect_err.IsEmpty() ) {
	    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT,
	    		connect_err,
	    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
	    );

	    return ExitCode(false);
    }
    
    // begin download, and notify the size of the file
    if (n>MAX_FILE_SIZE) { // some FTP Server doesn't provide file size
        EventCenter::post(wxEVT_INI_DOWNLOAD_BEGIN_EVENT, -1); // _("Unknown size"),
    } else {
    	EventCenter::post(wxEVT_INI_DOWNLOAD_BEGIN_EVENT, n);
    }
    
    if (0==n) {
	    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT,
	    		wxString::Format(_("Zero file size - url: %s"),mirror.c_str()),
	    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
	    );

	    return ExitCode(false);
	}
	
	while( !inet_stream.Eof() ) {
		if (!inet_stream.IsOk()) {
		    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT,
		    		wxString::Format(_("Download error - url: %s"), mirror.c_str()),
		    		inet_stream.GetLastError()==0?1:inet_stream.GetLastError()
		    );
		    
		    return ExitCode(false);
		}

        size_t len = inet_stream.Read(buf, BUF_SIZE).LastRead();
        downloaded = downloaded + len;
        
        setup_bz2.Write(buf, len);
        
        EventCenter::post(wxEVT_INI_DOWNLOADING_EVENT, downloaded);
		// wxMessageBox(wxString::Format(_("%d"), len));
	}
	
	// Moves the file pointer to the front 
	setup_bz2.Seek(0);
	
	wxFileInputStream temp_stream(setup_bz2);
	
	wxBZipInputStream bz_stream(temp_stream);
	if ( !bz_stream.IsOk() ) {
	    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT,
	    		wxString::Format(_("Can't read file: %s"), tempname.c_str()),
	    		bz_stream.GetLastError()==0?1:bz_stream.GetLastError()
	    );
	    
	    return ExitCode(false);
    }
	
	// wxMessageBox(wxString::Format(_T("%d"), inet_stream->GetSize()));
	IniBuilder& inibuilder = IniBuilder::get();
	inibuilder._clear_all();
	
    ini_init(bz_stream, inibuilder);
    
    if (yyparse() || yyerror_count > 0) {
	    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT,
	    		wxString::Format(_("Ini file parse error: %s"),tempname.c_str()),
	    		yyerror_count==0?1:yyerror_count
	    );
	    
	    return ExitCode(false);
    }

    inibuilder.pkg_names.Sort();
    inibuilder.cat_names.Sort();
    
    EventCenter::post(wxEVT_INI_DOWNLOAD_COMPLETED_EVENT, 0);
    
	setup_bz2.Close();
    ::wxRemoveFile(tempname);
	// wxMessageBox(tempname);
    
    inibuilder._parse_db();
    
    return ExitCode(true);
}


IniBuilder::~IniBuilder() {
}

// =================================================================

void IniBuilder::buildTimestamp (const char* time) {
    timestamp = strtoul(time, 0, 0);
    ::wxLogMessage(_T("setup-timestamp: %l"), timestamp);
}
void IniBuilder::buildVersion (const char* aVersion) {
    const wxString& version = wxString::FromAscii(aVersion);
    ::wxLogMessage(_T("setup-version: %s"), version.c_str() );

    if (version.size()) {
        if (version_compare(setup_version, version) < 0) {
            wxMessageBox( _T("The current ini file is from a newer version of setup.exe. \n"
                          "If you have any trouble installing, please download a fresh version from http://citrus.googlecode.com/"),
                          _T("Citrus Warning"), wxICON_EXCLAMATION | wxOK );
        }
    }
}
void IniBuilder::buildPackage (const char* aName) {
	_trust = TRUST_CURR;
	
    const wxString& name = wxString::FromAscii(aName);

    ::wxLogMessage(_T("@ %s"), name.c_str() );

    p_current_pkg = &packages[name];
    
    p_current_pkg->binary_status = PkgInfo::AVAILABLE;
    p_current_pkg->source_status = PkgInfo::INVALIDE;
    
    p_current_pkg->name = name;
    pkg_names.Add(name);

    return;
}
void IniBuilder::buildPackageVersion (const char* aVersion) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    p_current_pkg->version = wxString::FromAscii(aVersion);
    // ::wxLogMessage(_T("version: %s"), p_current_pkg->version.c_str() );

}
void IniBuilder::buildPackageSDesc (const char* theDesc) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    p_current_pkg->sdesc = wxString::FromAscii(theDesc);
    // wxLogMessage(_T("sdesc: %s"), p_current_pkg->sdesc.c_str() );
}
void IniBuilder::buildPackageLDesc (const char* theDesc) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    p_current_pkg->ldesc = wxString::FromAscii(theDesc);
    // wxLogMessage(_T("ldesc: %s"), p_current_pkg->ldesc.c_str() );
}
void IniBuilder::buildPackageInstall (const char* path) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    p_current_pkg->binary_path = wxString::FromAscii(path);
    // wxLogMessage(_T("path: %s"), p_current_pkg->binary_path.c_str() );
}
void IniBuilder::buildPackageSource (const char* path, const char* size) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }
    
    p_current_pkg->source_path = wxString::FromAscii(path);
    p_current_pkg->source_size = wxString::FromAscii(size);
    
    p_current_pkg->source_status = PkgInfo::AVAILABLE;
    
    // add source info
    // this->pkg_sources.Add(p_current_pkg->name);
    // wxLogMessage(_T("source: %s %s"), p_current_pkg->source_path.c_str(), p_current_pkg->source_size.c_str() );
}
void IniBuilder::buildPackageCategory (const char* name) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }
    
    wxString cat = wxString::FromAscii(name);

    if ( categories.find(cat) == categories.end() ) {
    	cat_names.Add(cat);
    }
    
    categories[cat].pkg_names.Add(p_current_pkg->name);
    
    p_current_pkg->category.Add(cat);
    // wxLogMessage(_T("category: %s"), cat.c_str() );
}
void IniBuilder::buildPackageRequire (const char* name) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }
    
    const wxString& require = wxString::FromAscii(name);
    
    // add dependent
    packages[require].dependent.Add(p_current_pkg->name);
    
    p_current_pkg->require.Add(require);
    // wxLogMessage(_T("require: %s"), require.c_str() );
}
void IniBuilder::buildInstallMD5 (char const * md5) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    // p_current_pkg->install_md5 = wxString::FromAscii(md5);
    // wxLogMessage(_T("install_md5: %s"), p_current_pkg->install_md5.c_str() );
}
void IniBuilder::buildInstallSize (const char* size) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    p_current_pkg->binary_size = wxString::FromAscii(size);
    // wxLogMessage(_T("binary_size: %s"), p_current_pkg->binary_size.c_str() );
}
void IniBuilder::buildSourceMD5 (char const * md5) {
    if (NULL==p_current_pkg || _trust!=TRUST_CURR) {
        return;
    }

    // p_current_pkg->source_md5 = wxString::FromAscii(md5);
    // wxLogMessage(_T("source_md5: %s"), p_current_pkg->source_md5.c_str() );
}
void IniBuilder::buildPackageTrust (IniBuilder::TRUST trust) {
	_trust = trust;
}

// =================================================================

PkgInfo::STATUS IniBuilder::get_pkg_status(const wxString& pkg) {
	if ( packages.find(pkg) == packages.end() ) {
		return PkgInfo::INVALIDE;
	}
	
	return packages[pkg].binary_status;
}
PkgInfo::STATUS IniBuilder::get_pkg_src_status(const wxString& pkg) {
	if ( packages.find(pkg) == packages.end() ) {
		return PkgInfo::INVALIDE;
	}
	
	return packages[pkg].source_status;
}
bool IniBuilder::set_pkg_status(const wxString& pkg, PkgInfo::STATUS status) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return false;
	}
	
	it->second.binary_status = status;
	return true;
}
bool IniBuilder::set_pkg_src_status(const wxString& pkg, PkgInfo::STATUS status) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return false;
	}
	
	it->second.source_status = status;
	return true;
}

// ===============================================================================

wxArrayString IniBuilder::get_pkg_all_dependents(const wxString& pkg) {
	wxArrayString dependents;
	
	_get_pkg_all_dependents(pkg, dependents);
	
	return dependents;
}
void IniBuilder::_get_pkg_all_dependents(const wxString& pkg, wxArrayString& dependents) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return;
	}
	
	const wxArrayString& dependent = it->second.dependent;
	
	for (int i=0; i<dependent.GetCount(); i++) {
		const wxString& dep = dependent[i];
		
		if (wxNOT_FOUND==dependents.Index(dep)) {
			dependents.Add(dep);
			_get_pkg_all_dependents(dep, dependents);
		}
	}
}
wxArrayString IniBuilder::get_pkg_all_requires(const wxString& pkg) {
	wxArrayString requires;
	
	_get_pkg_all_requires(pkg, requires);
	
	return requires;
}
void IniBuilder::_get_pkg_all_requires(const wxString& pkg, wxArrayString& requires) {
	
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return;
	}
	
	const wxArrayString& require = it->second.require;
	PkgInfo::STATUS status = it->second.binary_status;
	
	for (int i=0; i<require.GetCount(); i++) {
		const wxString& req = require[i];
		
		if ( wxNOT_FOUND==requires.Index(req) ) {
			requires.Add(req);
			_get_pkg_all_requires(req, requires);
		}
	}
}

bool IniBuilder::is_base_pkg(const wxString& pkg) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return false;
	}
	
	for (int i=0; i<it->second.category.GetCount(); i++) {
		if ( 0==it->second.category[i].CmpNoCase(_T("base")) ) {
			return true;
		}
	}
	
	return false;
}

bool IniBuilder::is_base_pkgs_installed() { // TODO
	/* InstalledBuilder& builder = InstalledBuilder::get();
	
	const wxArrayString& pkgs = categories[_T("Base")].pkg_names;
	
	for (int i=0; i<pkgs.GetCount(); i++) {
		int flag = builder.is_installed( pkgs[i] );
		
		if ( 0== (InstalledBuilder::BINARY_UPGRADABLE & flag)
			 && 0== (InstalledBuilder::BINARY_INSTALLED & flag) )
		{
			return false;
		}
	} */
	
	return true;
}

bool IniBuilder::mark_all_base_pkgs_installed() {
	
	const wxArrayString& pkgs = categories[_T("Base")].pkg_names;
	
	for (int i=0; i<pkgs.GetCount(); i++) {
		PkgInfo::STATUS status = self.get_pkg_status(pkgs[i]);
		
		// mark install for all requires
		const wxArrayString& requires = get_pkg_all_requires(pkgs[i]);
		for (int j=0; j<requires.GetCount(); j++) {
			PkgInfo::STATUS status2 = self.get_pkg_status(requires[j]);
			if (status2==PkgInfo::AVAILABLE) {
				self.set_pkg_status(requires[j], PkgInfo::XINSTALL);
				continue;
			}
			if (status2==PkgInfo::UPDATED) {
				self.set_pkg_status(requires[j], PkgInfo::REINSTALL);
				continue;
			}
			if (status2==PkgInfo::OUTDATED) {
				self.set_pkg_status(requires[j], PkgInfo::UPGRADE);
				continue;
			}
		}
		
		// mark install for the base package
		if (status==PkgInfo::AVAILABLE) {
			self.set_pkg_status(pkgs[i], PkgInfo::XINSTALL);
			continue;
		}
		if (status==PkgInfo::UPDATED) {
			self.set_pkg_status(pkgs[i], PkgInfo::REINSTALL);
			continue;
		}
		if (status==PkgInfo::OUTDATED) {
			self.set_pkg_status(pkgs[i], PkgInfo::UPGRADE);
			continue;
		}
	}
	
	return true;
}

bool IniBuilder::_parse_db() {
	MountTable& mt = MountTable::get();
	const wxString& path = mt.cygpath(CYGWIN_INSTALLED_DB_PATH);
	
	if ( false==wxFile::Exists(path) ) {
		return false;
	}
	
	 wxFFile dbfile(path);
	
	// init
	// _installed_hash.clear();
	 _installed_pkgs.Clear();
	
	wxString name;
	wxString filename;
	wxString number;
	
	
	char buf[2048] = {0};
	int i = 1;
	char c = 'x';
	buf[0] = c;
	
	while (!dbfile.Eof()) {
		int flag = 0;
		
		// parse a line
		while (i<2048) {
			dbfile.Read(&c, 1);
			
			if (' '==c) {
				buf[i] = '\0';
				
				switch (flag) {
					case 0 : // package name
						name = wxString::FromAscii(buf);
						break;
					case 1 : // package file name
						filename = wxString::FromAscii(buf);
						break;
					default :
						break; // will never reach here
				}

				flag++;
				i = 0;
				continue; // read next char
			} else if ( ( '\n'==c || '\r'==c || dbfile.Eof() ) && strlen(buf)>0 ) { // line separator
				buf[i] = '\0';
				number = wxString::FromAscii(buf);
				i = 0;
				
				// extract version from filename
				wxString version = filename.Mid(name.Len()+1, filename.Len()-name.Len()-wxStrlen(_T(".tar.gz"))-2);
				PkgInfo& pkg_info = packages[name];
				
				pkg_info.name = name;
				
				if ( version.Contains(_T("-src")) ) {
					version = version.Mid( 0, version.Len()-wxStrlen(_T("-src"))-1 );
					pkg_info.installed_src_version = version;
					pkg_info.installed_src_path = filename;
					
					if (version_compare(pkg_info.version, version) > 0) {
						pkg_info.source_status = PkgInfo::OUTDATED;
					} else  {
						pkg_info.source_status = PkgInfo::UPDATED;
					}
				} else {
					pkg_info.installed_bin_version = version;
					pkg_info.installed_bin_path = filename;
					
					if (version_compare(pkg_info.version, version) > 0) {
						pkg_info.binary_status = PkgInfo::OUTDATED;
					} else  {
						pkg_info.binary_status = PkgInfo::UPDATED;
					}
				}
				
				
				// _installed_hash[name] = pkg_info;
				
				_installed_pkgs.Add(name);
				
				buf[0] = '\0';
				break; // parse next line
			} else if (dbfile.Eof()) {
				break; // exit loop
			} else {
				buf[i] = c;
				i++;
			}
		} // end parse line
	}
	
	_installed_pkgs.Sort();
	
	return true;
}



wxArrayString IniBuilder::get_installed_files(const wxString& pkg) {
	wxArrayString files;
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return files;
	}
	
	wxString lst_gz_path = MountTable::get().cygpath(CYGWIN_INSTALLED_LST_DIR);
	lst_gz_path.Append(pkg);
	lst_gz_path.Append(_T(".lst.gz"));
	
	if ( !wxFile::Exists(lst_gz_path) ) {
		return files;
	}
	
	wxFileInputStream temp_stream(lst_gz_path);
	wxZlibInputStream gz_stream(temp_stream);

	if ( !gz_stream.IsOk() ) {
		return files;
	}

	const int BUF_SIZE = 2048;
	char buf[BUF_SIZE] = {0};
	char c;
	int i = 0;
	
	while ( !gz_stream.Eof() && i<BUF_SIZE ) {
		c = gz_stream.GetC();
		
		if ( ( '\n'==c || '\r'==c || gz_stream.Eof() ) && strlen(buf)>0 ) { // line separator
			buf[i] = '\0';
			
			const wxString& path = wxString::FromAscii(buf);
			i = 0; // parse next line
			
			//if ( !path.EndsWith(_T("/")) ) {
				// wxString native = MountTable::get().cygpath(path);
			files.Add(path);
			//}
			
			buf[0] = '\0';
		} else {
			buf[i] = c;
			i++;
		}
	}
	
	return files;
}


wxString IniBuilder::get_pkg_path(const wxString& pkg) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return wxString();
	}
	
	return it->second.binary_path;
}

wxString IniBuilder::get_pkg_size_str(const wxString& pkg) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return wxString();
	}
	
	return it->second.binary_size;
}

unsigned long IniBuilder::get_pkg_size(const wxString& pkg) {
	PkgHash::iterator it = packages.find(pkg);
	
	if ( it==packages.end() ) {
		return 0L;
	}

	unsigned long size = 0L;
	
	if ( false==it->second.binary_size.ToULong(&size) ) {
		return 0L;
	}
	
	return size;
}


wxString IniBuilder::get_installed_version(const wxString& name) {
	PkgHash::iterator it = packages.find(name);
	
	if (it == packages.end() ) {
		return wxString();
	}
	
	return it->second.installed_bin_version;
}

bool IniBuilder::set_installed(const wxString& pkg) {
	PkgHash::iterator it = packages.find(pkg);
	
	if (it == packages.end() ) {
		return false;
	}

	it->second.binary_status = PkgInfo::UPDATED;
	it->second.installed_bin_version = it->second.version;
	it->second.installed_bin_path = it->second.binary_path;
	return true;
}
bool IniBuilder::set_removed(const wxString& pkg) {
	PkgHash::iterator it = packages.find(pkg);
	
	if (it == packages.end() ) {
		return false;
	}
	
	it->second.binary_status = PkgInfo::AVAILABLE;
	it->second.installed_bin_version = wxEmptyString;
	it->second.installed_bin_path = wxEmptyString;
	return true;
}

bool IniBuilder::regenerate_db() {
	wxString dbpath = MountTable::get().cygpath(_T("/etc/setup"));
	wxFileName::Mkdir(dbpath, 0777, wxPATH_MKDIR_FULL);
	dbpath.Append(_T("/installed.db"));
	
	wxFFile dbfile(dbpath, _T("w+b"));
	dbfile.Write(_T("INSTALLED.DB 2\n"));
	
	PkgHash::iterator it;
	
	for (it=packages.begin(); it!=packages.end(); it++) {
		if ( it->second.installed_bin_version.IsEmpty() ) {
			continue;
		}
		
		dbfile.Write(it->first);
		dbfile.Write(_T(" "));
		
		const wxString& bzname = wxFileName(it->second.installed_bin_path).GetFullName();
		dbfile.Write(bzname);
		
		dbfile.Write(_T(" 0\n"));
	}
	
	dbfile.Close();
	
	// reload db file
	_parse_db();
}

IniBuilder IniBuilder::self;
